/*
 * @(#)GOManager.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



package gameobject;

//~--- non-JDK imports --------------------------------------------------------

import core.Main;

//~--- JDK imports ------------------------------------------------------------

import java.io.Serializable;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.JMException;

/**
 * Creates, deletes, and tracks {@linkplain gameobject.GOManager.GameObject GameObjects}.
 * @author rick
 */
public class GOManager implements Serializable {

    /** Field description */
    private Map<UUID, GameObject>	objects;

    /** Field description */
    private Map<String, UUID>	named_objects;

    /**
     * Creates objects and named_objects maps.
     */
    public GOManager() {
	objects		= new HashMap<>();
	named_objects	= new HashMap<>();
    }

    /**
     * Creates a component of type 'type_name' and adds it to the GO 'id'.
     * If the type_name doesn't begin with 'gameobject.' this prefix is added to
     * the type_name. Also calls addComponent for all components the new
     * component requires.
     * @param type_name The type_name of the component to add.
     * @param id The id of the GO
     * @return Always returns true
     * @throws Exception Throws if 'gameobject.type_name' is not a valid
     * component type, if addComponent fails for one of the required
     * components, if a new instance can't be created, or if id is not a
     * valid GO.
     */
    public boolean addComponent(String type_name, UUID id) throws Exception {
	GameObject	obj	= getObjectFromUUID(id);

	// Component names need to begin with gameobject.
	if (!type_name.startsWith("gameobject.")) {
	    type_name	= "gameobject." + type_name;
	}

	// Get the Class of the component from the script manager
	Class	new_class	= Class.forName(type_name, true, Main.getScriptManager().getGroovyClassLoader());

	// Create a new instance
	Component	new_inst	= (Component) new_class.newInstance();

	// Give the component a handle to its owner
	new_inst.setOwner(new GOHandle(id));

	// Add the component to the GO
	obj.addComponent(type_name, new_inst);

	// Add all required component
	Set<String>	required	= new_inst.getRequiredComponents();

	for (String s : required) {
	    addComponent(s, id);
	}

	return true;
    }

    /**
     * Returns a GOHandle to the object with name 'name'. Failure to find the
     * given named object is not indicated. Case Sensitive.
     * @param name The name of the GO
     * @return A GOHandle that either points to the GO or nothing.
     */
    public GOHandle getNamedObject(String name) {
	return new GOHandle(named_objects.get(name));
    }

    /**
     * Checks whether a GO with name 'name' exists. Case sensitive.
     * @param name The name of the GO
     * @return True if it exists or false otherwise.
     */
    public boolean hasNamedObject(String name) {
	return named_objects.containsKey(name);
    }

    /**
     * Associates the string 'name' with the GO 'id'. Case Sensitive.
     * @param name The desired name of the GO
     * @param id The id of the GO
     * @return False if the name is already in use or 'id' is not valid. True
     * otherwise.
     */
    public boolean registerNamedObject(String name, UUID id) {
	if (hasNamedObject(name) ||!isValid(id)) {
	    return false;
	}

	named_objects.put(name, id);

	return true;
    }

    /**
     * Disassociates the string 'name' with its GO.
     * @param name The name to disassociate
     */
    public void deregisterNamedObject(String name) {
	named_objects.remove(name);
    }

    /**
     * Creates a new empty GO and returns a GOHandle to it.
     * @return A GOHandle to the newly created GO.
     * @throws Exception If GameObject constructor fails.
     */
    public GOHandle createGO() throws Exception {
	GameObject	as_go	= new GameObject();
	UUID		new_id	= UUID.randomUUID();

	as_go.setUUID(new_id);
	objects.put(new_id, as_go);

	return new GOHandle(new_id);
    }

    /**
     * Creates a new GO and adds the component types indicated in component_types.
     *
     * @param component_types A list of the names of components to add.
     * @return A GOHandle to the newly created GO.
     * @throws Exception If any of the components can't be added or if GameObject() fails.
     */
    public GOHandle createGO(String[] component_types) throws Exception {
	GameObject	as_go	= new GameObject();
	UUID		new_id	= UUID.randomUUID();

	as_go.setUUID(new_id);
	objects.put(new_id, as_go);

	for (String s : component_types) {
	    addComponent(s, new_id);
	}

	return new GOHandle(new_id);
    }

    /**
     * Adds the GO to objects with its ID as the key. This should only be called by GameObject.
     * @param object The GO to register.
     */
    public void registerGO(GameObject object) {
	objects.put(object.getUUID(), object);
    }

    /**
     * Removes the GO with ID from objects. If the GO is named it does not
     * remove it from named_objects. This should only be called by GameObject.
     * @param object The GO to deregister.
     */
    public void deregisterGO(GameObject object) {
	objects.remove(object.getUUID());
    }

    /**
     * Returns the GameObject with id 'id'.
     * @param id The desired Go's id.
     * @return The GameObject with id 'id'.
     * @throws Exception If the id is not valid.
     */
    public GameObject getObjectFromUUID(UUID id) throws Exception {
	if (!isValid(id)) {
	    throw new JMException("Invalid GO id");
	}

	return objects.get(id);
    }

    /**
     * Checks if the id refers to a valid GO.
     * @param id The id to check.
     * @return True if it is valid, false otherwise.
     */
    public boolean isValid(UUID id) {
	return objects.containsKey(id);
    }

    /**
     * Checks if the passed GO is registered.
     * @param id The GameObject to check.
     * @return True if the GO is registered, false otherwise.
     */
    public boolean isValid(GameObject id) {
	return objects.containsKey(id.getUUID());
    }

    /**
     *
     */
    public static class GameObject implements Serializable {

	/** Field description */
	private Map<String, Component>	components;

	/** Field description */
	private UUID	id;

	/**
	 * Creates a new GO and creates a new HashMap for components.
	 */
	protected GameObject() {
	    components	= new HashMap<>();
	}

	/**
	 * Sets this gameobject's id.
	 * @param id The new ID
	 */
	protected void setUUID(UUID id) {
	    this.id	= id;
	}

	/**
	 * Adds the passed component. If the type_name does not begin with
	 * gameobject. it adds that prefix to the name. If a component of
	 * the given type already exists it is overwritten with the new
	 * component.
	 * @param type_name The component type name
	 * @param component The component to add
	 * @return Always returns true.
	 */
	public boolean addComponent(String type_name, Component component) {

	    // Component names need to begin with gameobject.
	    if (!type_name.startsWith("gameobject.")) {
		type_name	= "gameobject." + type_name;
	    }

	    components.put(type_name, component);

	    return true;
	}

	/**
	 * Checks if a component of type name exists on this GO. If name does
	 * not begin with gameobject. this prefix is added.
	 * @param name The name of the component type
	 * @return True if an instance of the type exists. False otherwise.
	 */
	public boolean hasComponent(String name) {

	    // Component names need to begin with gameobject.
	    if (!name.startsWith("gameobject.")) {
		name	= "gameobject." + name;
	    }

	    return components.containsKey(name);
	}

	/**
	 * Returns the instance of the component with type name. If name
	 * does not begin with gameobject. this prefix is added.
	 * @param name The component type name.
	 * @return The component of type 'name' or null if it doesn't exist.
	 */
	public Component getComponent(String name) {
	    if (!name.startsWith("gameobject.")) {
		name	= "gameobject." + name;
	    }

	    return components.get(name);
	}

	/**
	 * Prints all component type names to the log file.
	 */
	public void printComponents() {
	    for (Map.Entry<String, Component> entry : components.entrySet()) {
		Logger.getLogger(GameObject.class.getName()).log(Level.SEVERE, "Component ", entry.getKey());
	    }
	}

	/**
	 * Calls GOManager::deregisterGO(this)
	 */
	public void deregister() {
	    Main.getWorld().getGOManager().deregisterGO(this);
	}

	/**
	 * Checks to see if this GO is registered with GOManager
	 * @return True if valid, false otherwise.
	 */
	public boolean isRegistered() {
	    return Main.getWorld().getGOManager().isValid(this);
	}

	/**
	 * Returns this GO's id.
	 * @return This GO's id.
	 */
	public UUID getUUID() {
	    return id;
	}
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
